use super::{
    json, util::replace_value, Base, Client, MapSv, RNull, Resp, SvCache, VBase, VMap, Value,
};

pub struct Plc {
    id: String,             // plc id
    ip: String,             // plc ip
    tp: String,             // plc type
    stp: String,            // s7 type
    itp: String,            // iterface type
    rank: i64,              // s7 use it
    slot: i64,              // s7 use it
    trigger: Option<Value>, // itp == trigger, set it
    data: MapSv,            // iterface data
    client: Client,
}

impl Base for Plc {}

impl Plc {
    pub fn new(data: &MapSv, plc: &MapSv, client: &Client) -> Result<Self, Value> {
        let ip = plc.ck_str("ip")?;
        let id = plc.ck_str("id")?;
        let tp = plc.ck_str("type")?;
        let rank = match plc.k_i64("rank") {
            Some(v) => v,
            None => 0,
        };
        let slot = match plc.k_i64("slot") {
            Some(v) => v,
            None => 0,
        };
        let stp = match plc.k_str("tp") {
            Some(v) => v,
            None => "",
        };

        let itp = data.ck_str("tp")?;
        let trigger = match data.k_str("trigger") {
            Some(k) => {
                let m = data.ck_str("m")?;
                let t = match data.k_str("tr_t") {
                    Some(v) => v,
                    None => "bool",
                };

                if t == "bool" {
                    Some(json!({
                        "k": k,
                        "m": m,
                        "t": t,
                    }))
                } else {
                    let v = data.ck_i64("tr_v")?;
                    Some(json!({
                        "k": k,
                        "m": m,
                        "t": t,
                        "v": v,
                    }))
                }
            }
            None => None,
        };

        Ok(Plc {
            ip: ip.to_string(),
            id: id.to_string(),
            tp: tp.to_string(),
            itp: itp.to_string(),
            stp: stp.to_string(),
            rank,
            slot,
            trigger: trigger,
            data: data.clone(),
            client: client.clone(),
        })
    }

    pub fn get_cache_id(&self) -> Result<String, Value> {
        let v = self.data.ck_str("uid")?;
        Ok(v.to_string())
    }

    pub fn get_state_id(&self) -> Result<String, Value> {
        let v = self.get_cache_id()?;
        Ok(format!("st.{v}"))
    }

    pub fn is_trigger(&self) -> bool {
        self.itp == "trigger"
    }

    pub fn is_timer(&self) -> bool {
        self.itp == "timer"
    }

    pub fn get_id(&self) -> String {
        self.id.clone()
    }

    pub async fn wplc(&self, change: &MapSv) -> RNull {
        let tmp = match self.data.k_bool("wplc") {
            Some(v) => v,
            None => false,
        };

        if !tmp {
            return Ok(());
        }

        let body = match self.data.k_array("w") {
            Some(v) => v,
            None => {
                return Ok(());
            }
        };

        let mut data = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            if let Some(v) = replace_value(change, d) {
                let mut tmp = d.clone();

                tmp.insert("v".to_string(), json!([v]));

                data.push(tmp);

                continue;
            }

            data.push(d.clone());
        }

        let url = format!("{}/write", self.tp);

        let cmd = json!({
            "id": self.id,
            "data": data,
        });

        let _ = self.client.netplc(&cmd, &url).await?;

        Ok(())
    }

    pub async fn read_trigger(&self, real: &SvCache) -> Result<bool, Value> {
        let cache_id = self.get_cache_id()?;

        let d = match &self.trigger {
            Some(v) => v,
            None => {
                return Err(json!("null"));
            }
        };

        let m = d.ck_str("m")?;
        let url = format!("{}/read", self.tp);

        let cmd = json!({
            "id": self.id,
            "data": vec![d],
        });

        let body = self.client.netplc(&cmd, &url).await?;

        let val = body.ck_obj("data")?.ck_val(m)?;

        let real_id = format!("tr.{cache_id}.{m}");
        real.insert(real_id, val.clone()).await;

        if let Some(ret) = val.as_bool() {
            return Ok(ret);
        }

        let ret = val.cki64()?;
        let v = d.ck_i64("v")?;

        Ok(ret == v)
    }

    pub async fn connect(&self) -> Resp {
        let url = format!("{}/connect", self.tp);

        let cmd = json!({
            "id": self.id,
            "ip": self.ip,
            "tp": self.stp,
            "rank": self.rank,
            "slot": self.slot,
        });

        self.client.netplc(&cmd, &url).await
    }

    pub async fn read_plc(&self) -> Result<MapSv, Value> {
        let val = self.data.ck_array("v")?;
        let url = format!("{}/read", self.tp);

        let cmd = json!({
            "id": self.id,
            "data": val,
        });

        let body = self.client.netplc(&cmd, &url).await?;
        let body = body.ck_obj("data")?;

        Ok(body.clone())
    }

    fn db_gen_val(&self, gen: &MapSv, val: &mut MapSv) {
        for (k, v) in gen.iter() {
            if k == "psn" || k == "wsn" || k == "line" {
                continue;
            }

            if let Some(v) = v.as_array() {
                let mut tmp = Vec::new();
                for d in v.iter() {
                    tmp.push(d.to_string());
                }

                let t = tmp.join(",");

                val.insert(k.to_string(), json!(t));

                continue;
            }

            val.insert(k.to_string(), json!(v));
        }
    }

    fn db_gen_data(&self, gen: &MapSv) -> MapSv {
        let mut data = MapSv::new();

        if let Some(psn) = gen.k_str("psn") {
            data.insert("psn".to_string(), json!(psn));
        }

        if let Some(wsn) = gen.k_str("wsn") {
            data.insert("wsn".to_string(), json!(wsn));
        }

        data
    }

    fn db_gen_cmd(&self, gen: &MapSv, tab: &str, ck: bool) -> MapSv {
        let mut data = MapSv::new();

        data.insert("tab".to_string(), json!(tab));

        let line = match gen.k_str("line") {
            Some(v) => v,
            None => "dcs",
        };
        data.insert("line".to_string(), json!(line));

        if ck {
            data.insert("ck".to_string(), json!(false));
        }

        data
    }

    pub async fn save_db(&self, change: &MapSv) -> RNull {
        let db_save = match self.data.k_bool("db_save") {
            Some(v) => v,
            None => false,
        };

        if !db_save {
            return Ok(());
        }

        let mut gen = MapSv::new();

        if let Some(f) = self.data.k_obj("f") {
            for (k, v) in f.iter() {
                gen.insert(k.to_string(), json!(v));
            }
        }

        for (k, val) in change.iter() {
            if let Some(_) = val.as_object() {
                continue;
            }

            if let Some(_) = val.first_obj() {
                continue;
            }

            gen.insert(k.to_string(), json!(val));
        }

        let tab = self.data.ck_str("uid")?;

        let mut cmd = self.db_gen_cmd(&gen, tab, true);
        let mut data = self.db_gen_data(&gen);

        let mut val = MapSv::new();
        self.db_gen_val(&gen, &mut val);

        data.insert("v".to_string(), json!(val));

        cmd.insert("data".to_string(), json!([data]));

        let cmd = json!(cmd);
        let _ = self.client.timedb(&cmd, "gchunk").await?;

        Ok(())
    }

    pub async fn save_db_pure(&self, cmd: &Value) -> Resp {
        self.client.timedb(&cmd, "gchunk").await
    }

    pub async fn db_plc(&self, tag: &str) -> RNull {
        let db_save = match self.data.k_bool("db_save") {
            Some(v) => v,
            None => false,
        };

        if !db_save {
            return Ok(());
        }

        self.write_plc(tag).await
    }

    async fn write_plc(&self, tag: &str) -> RNull {
        let body = match self.data.k_array(tag) {
            Some(v) => v,
            None => {
                return Ok(());
            }
        };

        let url = format!("{}/write", self.tp);

        let cmd = json!({
            "id": self.id,
            "data": body,
        });

        let _ = self.client.netplc(&cmd, &url).await?;

        Ok(())
    }
}
